from ticket.utils import qualified_name
from django.core.urlresolvers import get_callable

TICKET_TESTS="""
given a django model instance and a function that has to act upon it, we can enqueue the function to be deferred to later. We could do this with pickled functions or something, i suppose. but then, why ?

>>> from ticket.models import Ticket
>>> from ticket.tests.models import BoringModel, preen, temporary_fail_to_preen, permanent_fail_to_preen, unexpected_fail_to_preen
>>> a=BoringModel(name='alpha')
>>> a.save()
>>> b=BoringModel(name='beta')
>>> b.save()
>>> t=Ticket.objects.enqueue(a, preen)
>>> t
<Ticket: ticket.tests.models.preen queued for BoringModel instance with pk 1>
>>> t.function
'ticket.tests.models.preen'
>>> Ticket.objects.enqueue(a, preen)
<Ticket: ticket.tests.models.preen queued for BoringModel instance with pk 1>
>>> Ticket.objects.all().filter_by_func(preen).count()
2

the idempotent queue method is a get_or_create analogue. it won't add things to the queue if they already exist.
>>> Ticket.objects.enqueue_idempotent(a, preen)
<Ticket: ticket.tests.models.preen queued for BoringModel instance with pk 1>
>>> Ticket.objects.all().filter_by_func(preen).count()
2

But it will if they don't
>>> Ticket.objects.enqueue_idempotent(b, preen)
<Ticket: ticket.tests.models.preen queued for BoringModel instance with pk 2>
>>> Ticket.objects.all().filter_by_func(preen).count() 
3

process_next() pops a ticket instance off the queue, and executes the queued function.
it also returns the instance, for ease of debugging.
>>> ticket=Ticket.objects.all().filter_by_func(preen).process_next(order_by='pk')
preened BoringModel with pk 1
>>> ticket.inst.preened
True

Successful dequeueings remove a ticket
>>> Ticket.objects.all().filter_by_func(preen).count() #dequeue success
2

Temporarily unsuccessful dequeueings do not.
>>> Ticket.objects.enqueue(a, temporary_fail_to_preen)
<Ticket: ticket.tests.models.temporary_fail_to_preen queued for BoringModel instance with pk 1>
>>> Ticket.objects.all().filter_by_func(temporary_fail_to_preen).count() #dequeue failure in 3..2..1..
1
>>> ticket=Ticket.objects.all().filter_by_func(temporary_fail_to_preen).process_next(order_by='pk')
>>> Ticket.objects.all().filter_by_func(temporary_fail_to_preen).count() #dequeue failure
1

Permanently unsuccessful dequeueings leave them in but set them inactive - which means the methods on the manager won't pull them out by default.
>>> Ticket.objects.enqueue(a, permanent_fail_to_preen)
<Ticket: ticket.tests.models.permanent_fail_to_preen queued for BoringModel instance with pk 1>
>>> Ticket.objects.all().filter_by_func(permanent_fail_to_preen).count()
1
>>> ticket=Ticket.objects.all().filter_by_func(permanent_fail_to_preen).process_next(order_by='pk')
>>> Ticket.objects.all().filter_by_func(permanent_fail_to_preen).count()
1
>>> Ticket.objects.filter_by_func(permanent_fail_to_preen).count()
0

Unhandled exceptions in tickets are Bad. They should possibly be TransientFailures, but not handled properly - or they may be something going completely wrong. We allow behaviour selectable by a global var.
>>> from django.conf import settings
>>> t=Ticket.objects.enqueue(a, unexpected_fail_to_preen)
>>> settings.TICKETS_RAISE_UNCAUGHT_EXCEPTIONS=True
>>> try:
...     Ticket.objects.filter_by_func(unexpected_fail_to_preen).process_next()
... except Exception, exc:
...     print exc._ticket==t #attach ticket to exception for convenience
True
>>> settings.TICKETS_RAISE_UNCAUGHT_EXCEPTIONS=False
>>> r=Ticket.objects.filter_by_func(unexpected_fail_to_preen).process_next()
>>> r.status
'UNCAUGHT_EXCEPTION'


Deleted instances cause their queued tickets to fail silently:
>>> Ticket.objects.enqueue_idempotent(b, preen)
<Ticket: ticket.tests.models.preen queued for BoringModel instance with pk 2>
>>> b.delete()
>>> Ticket.objects.all().filter_by_inst(b).process_next(order_by='pk')

we provide Queue as a more readable alias to the Ticket manager:
>>> from ticket import Queue
>>> Queue is Ticket.objects
True

process_next() methods should exist on the manager *and* the queryset.
"""

IMPORT_TESTS="""
return an import-string-style name for a function or class
confusingly this test is written to be executed from the test
module. really should make it easier to extend test suite

>>> from ticket.tests.models import test_func
>>> func_name=qualified_name(test_func)
>>> func_name
'ticket.tests.models.test_func'
>>> get_callable(func_name)==test_func
True
"""


__test__ = {
  'ticket': TICKET_TESTS,
  'import_tests': IMPORT_TESTS,
}

